﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Centrify.DirectAudit.Common.Terminal;
using Poderosa;
using Poderosa.Document;
using Poderosa.View;
using Point = System.Windows.Point;
using Size = System.Drawing.Size;

namespace Player
{
    public abstract class RenderEngineBase
    {
        public event EventHandler ImageSourceUpdated;

        private BitmapSource m_image;
        public BitmapSource Image
        {
            get { return m_image; }
            protected set
            {
                m_image = value;
                OnImageSourceUpdated(this, new EventArgs());
            }
        }

        public abstract void SuspendScreenUpdate();
        public abstract void ResumeScreenUpdate();

        public abstract void Process(FrameBase frame);
        public abstract BitmapSource TakeSnapshot();

        protected void OnImageSourceUpdated(object sender, EventArgs e)
        {
            if (ImageSourceUpdated != null)
            {
                ImageSourceUpdated(sender, e);
            }
        }
    }

    public class WinRenderEngine : RenderEngineBase
    {
        private System.Drawing.Size m_previousIframeSize = System.Drawing.Size.Empty;

        // Freezable object to be bound by Image UIElement
        WriteableBitmap m_writeableBitmap;

        Size m_size;
        int m_bytesPerPixel;
        byte[] m_palette;
        CaptureContract.PixelFormat m_format;

        /// <summary>
        /// Render engine convert the captured RFB stream into a BitmapSource, which is used
        /// to display image on the screen.
        /// </summary>
        /// <param name="size">How big the screen is</param>
        /// <param name="pf">Screen image format used by the render engine (not the format of the rfb stream)</param>
        /// <param name="palette">Color palette used by the rfb stream - null for true color rfb streams</param>
        /// <param name="format">Format is the pixel format of the rfb data</param>
        [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
        public WinRenderEngine(Size size, PixelFormat pixelFormat, byte[] palette, CaptureContract.PixelFormat format)
        {
            // Pbgra32 is native to WritabeableBitmap and has better performance
            // DPI is really for us to determine the size of the image on the screen.
            // We don't care and let's default it to 96 for now.  We don't care because
            // winreplay allows user to resize the screen anyway.  I think the most
            // sensible one is to default it to the Windows system DPI setting and we have
            // a feature to show the real pixels.
            m_writeableBitmap = (size == Size.Empty) ? new WriteableBitmap(1024, 768, 96, 96, pixelFormat, null) : new WriteableBitmap(size.Width, size.Height, 96, 96, pixelFormat, null);
            Image = m_writeableBitmap;

            // TODO: After we figure out how to convert palette to BitmapPalette
            // we can combine the PixelFormat with CaptureContract.PixelFormat.
            // At that time, we just pass in the BitmapPalette into WritableBitmap
            // and WPF will solve the problem for us
            // i.e. let WPF to do the image format conversion for us.
            // 
            // However, I am not sure if WPF will use parallel extension to improve
            // the performance.  Doing it ourselves has an advantage that we can
            // optimize the conversion
            m_size = size;
            m_format = format;
            m_palette = palette;
            m_size = size;
            m_bytesPerPixel = (pixelFormat.BitsPerPixel + 7) / 8;
        }

        public override void SuspendScreenUpdate()
        {
            Image = m_writeableBitmap.CloneCurrentValue();
        }

        public override void ResumeScreenUpdate()
        {
            Image = m_writeableBitmap;
        }

        public override void Process(FrameBase frame)
        {
            Frame thisFrame = frame as Frame;
            if (thisFrame == null) return; // TODO: Do the checking on Noop            

            // We'll do lots of byte[] allocating as we decode/decompress individual rectangles,
            // do so into a thread-specific pool of cached byte buffers.
            CachedBufferThreadCollection.Push();

            foreach (RfbRect rect in thisFrame.Rectangles(true))
            {
                //Debug.WriteLine(string.Format("screen update rect:{0}, {1}-----rect type:{2} ---- frame type:{3}", rect.Size.Width, rect.Size.Height, rect.StoreEncoding, frame.Type));
                if (rect.StoreEncoding == CaptureContract.StoredEncoding.DesktopResize)
                {
                    //if DesktopResize
                    DoResizeImage(rect.Size);

                    m_previousIframeSize = rect.Size;
                    m_size = rect.Size;
                }
                else if (thisFrame.Type == 0)
                {
                    //if it is a i-frame and current rect size not equals previous i-frame size
                    if (m_previousIframeSize != Size.Empty && m_previousIframeSize != rect.Size)
                    {
                        DoResizeImage(rect.Size);
                        m_size = rect.Size;
                    }

                    m_previousIframeSize = rect.Size;
                }

                if (rect.StoreEncoding == CaptureContract.StoredEncoding.Raw ||
                     rect.StoreEncoding == CaptureContract.StoredEncoding.CopyRect)
                {
                    ShowRect(rect);
                }
            }

            CachedBufferThreadCollection.Pop();
        }

        public override BitmapSource TakeSnapshot()
        {
            return m_writeableBitmap.CloneCurrentValue();
        }

        // unsafe becuase we do ptr juggling
        unsafe protected void ShowRect(RfbRect rect)
        {
            // we use lockbits and directly modify the returned byte array
            // if use the 'proper' get and set pixel methods it takes 10 times longer            

            Rectangle lockrect = new Rectangle(new System.Drawing.Point(0, 0), m_size);
            Rectangle drawRect = new Rectangle(rect.Location, rect.Size);
            if (!lockrect.Contains(drawRect))
            {
                drawRect.Intersect(lockrect);
                rect.Location = drawRect.Location;
                rect.Size = drawRect.Size;
            }

            m_writeableBitmap.Lock();
            try
            {
                UpdateScreen(rect, (byte*)m_writeableBitmap.BackBuffer, m_writeableBitmap.BackBufferStride);
                m_writeableBitmap.AddDirtyRect(new System.Windows.Int32Rect(rect.Location.X, rect.Location.Y, rect.Size.Width, rect.Size.Height));
            }
            finally
            {
                m_writeableBitmap.Unlock();
            }
        }

        // why is this a separate public method - it might be useful for other rendering systems that
        // have their own raw bitmap data 
        unsafe protected void UpdateScreen(RfbRect rect, byte* pd, int stride)
        {
            RfbRectRaw raw = rect as RfbRectRaw;
            if (raw != null)
            {
                // its raw pixel data. blit it to the screen                
                int pixoff = 0; // where we are in the RFC pixel stream

                // its probably possible to do this more efficiently but it doesnt seem to be a bottleneck at the moment
                // It's a bottleneck now because we are rendering multiple pictures at the same time for the search result
                // All the rendering work are serialized to UI thread.
                for (int i = 0; i < raw.Size.Height; i++)
                {
                    for (int j = 0; j < raw.Size.Width; j++)
                    {
                        int poff = (stride * (raw.Location.Y + i)) + (j + raw.Location.X) * m_bytesPerPixel;// offset to the pixel in the screen buffer
                        // the RGB values to write there
                        int blue = 0;
                        int green = 0;
                        int red = 0;
                        ushort pixel;
                        switch (m_format)
                        {
                            case CaptureContract.PixelFormat.Mapped1Byte:
                                Debug.Assert(m_palette != null);// we gotta have a palette!

                                // the palette has a 4 byte header and is then an array of 6 byte color values
                                // the color values are RRGGBB (ie 16 bits per channel)
                                // TODO - make sure that the pixel values are inside the color map (the size is in the header

                                int paloff = (raw.Pixels[pixoff++] * 6) + 4;// +4 skips the palette header. paloff points to the correct RRGGBB entry

                                // get each channel value
                                ushort rpal = BitConverter.ToUInt16(m_palette, paloff);
                                paloff += sizeof(Int16);
                                ushort gpal = BitConverter.ToUInt16(m_palette, paloff);
                                paloff += sizeof(Int16);
                                ushort bpal = BitConverter.ToUInt16(m_palette, paloff);

                                red = (rpal >> 8) & 0xff;
                                green = (gpal >> 8) & 0xff;
                                blue = (bpal >> 8) & 0xff;

                                break;

                            case CaptureContract.PixelFormat.Raw2Byte555:

                                // the next three are basically the same.
                                // mask and shift out the channel values
                                // the exact masdks and shift vary depending on the input pixel format

                                // this is 16bytes per pixel with 5 bits per channel

                                pixel = BitConverter.ToUInt16(raw.Pixels, pixoff);
                                pixoff += 2;
                                red = ((pixel >> 10) & 0x1F) << 3;
                                green = ((pixel >> 5) & 0x1F) << 3;
                                blue = ((pixel >> 0) & 0x1F) << 3;
                                break;

                            case CaptureContract.PixelFormat.Raw2Byte565:

                                // 16 bits per pixel with 5 bits for red, 6 for blue and 5 for green

                                pixel = BitConverter.ToUInt16(raw.Pixels, pixoff);
                                pixoff += 2;
                                red = ((pixel >> 11) & 0x1F) << 3;
                                green = ((pixel >> 5) & 0x3F) << 2;
                                blue = ((pixel >> 0) & 0x1F) << 3;
                                break;

                            case CaptureContract.PixelFormat.Raw4Byte888:

                                // 24 bits per pixel. 8 bites per channel

                                blue = raw.Pixels[pixoff++];
                                green = raw.Pixels[pixoff++];
                                red = raw.Pixels[pixoff++];
                                pixoff++; // the stored stream is actually 4 bytes per pixel - we dont need the last one
                                // TODO - so lets stop storing it!
                                break;
                            default:
                                Debug.Assert(false);
                                break;
                        }
                        // put the 24 bits of color into our screen image

                        pd[poff++] = (byte)blue;
                        pd[poff++] = (byte)green;
                        pd[poff++] = (byte)red;
                        pd[poff] = 255;

                        // done 1 pixel
                    }
                }
            }
            else
            {
                RfbRectCopyRect cr = rect as RfbRectCopyRect;
                Debug.Assert(cr != null); // we only support 2 formats
                //Debug.WriteLine(String.Format("CopyRect L={0}, S={1}, Src={2}", cr.Location, cr.Size, cr.Source));

                // just like memmove we have to be aware of overlaps and do them in the correct order
                // this can probably be made much more efficient by copying in chunks
                // but this doesnt seem to be a bottleneck at the moment

                // TBD: would be really nice if we could BitBlt via the Bitmap control.. Is that possible?                    

                Rectangle sourceRect = new Rectangle(cr.Source, cr.Size);
                Rectangle destRect = new Rectangle(cr.Location, cr.Size);

                // If they don't intersect, it doesn't matter - just iterate over the source and put the bits in place
                if (!sourceRect.IntersectsWith(destRect))
                {
                    for (int i = 0; i < cr.Size.Height; i++)
                    {
                        for (int j = 0; j < cr.Size.Width; j++)
                        {
                            int poff = (stride * (cr.Location.Y + i)) + (j + cr.Location.X) * m_bytesPerPixel;
                            int psrc = (stride * (cr.Source.Y + i)) + (cr.Source.X + j) * m_bytesPerPixel;
                            pd[poff++] = pd[psrc++];
                            pd[poff++] = pd[psrc++];
                            pd[poff++] = pd[psrc];
                        }
                    }
                }
                else
                {
                    // Double buffer                    
                    using (CachedBuffer cb = CachedBufferAllocator.GetCachedBuffer(destRect.Height * stride))
                    {
                        byte[] buff = cb.Buffer;

                        // Copy to buffer
                        for (int i = 0; i < cr.Size.Height; i++)
                        {
                            for (int j = 0; j < cr.Size.Width; j++)
                            {
                                int poff = (stride * (i)) + (j) * m_bytesPerPixel;
                                int psrc = (stride * (cr.Source.Y + i)) + (cr.Source.X + j) * m_bytesPerPixel;
                                buff[poff++] = pd[psrc++];
                                buff[poff++] = pd[psrc++];
                                buff[poff++] = pd[psrc];
                            }
                        }

                        // Copy back to pixels
                        for (int i = 0; i < cr.Size.Height; i++)
                        {
                            for (int j = 0; j < cr.Size.Width; j++)
                            {
                                int poff = (stride * (cr.Location.Y + i)) + (j + cr.Location.X) * m_bytesPerPixel;
                                int psrc = (stride * (i)) + (j) * m_bytesPerPixel;
                                pd[poff++] = buff[psrc++];
                                pd[poff++] = buff[psrc++];
                                pd[poff++] = buff[psrc];
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Resize ImageSource with the specified width/height
        /// </summary>
        /// <param name="source">Source image to resize</param>
        /// <param name="width">Width of resized image</param>
        /// <param name="height">Height of resized image</param>
        /// <returns>Resized image</returns>
        private static WriteableBitmap ResizeImage(BitmapSource source, int width, int height)
        {
            // Target Rect for the resize operation
            System.Windows.Rect rect = new System.Windows.Rect(0, 0, width, height);

            // Create a DrawingVisual/Context to render with
            DrawingVisual drawingVisual = new DrawingVisual();
            using (DrawingContext drawingContext = drawingVisual.RenderOpen())
            {
                drawingContext.DrawImage(source, rect);
            }

            // Use RenderTargetBitmap to resize the original image
            RenderTargetBitmap resizedImage = new RenderTargetBitmap(
                (int)rect.Width, (int)rect.Height,
                source.DpiX, source.DpiY,
                source.Format);
            resizedImage.Render(drawingVisual);

            // Return the resized image
            return new WriteableBitmap(resizedImage);
        }

        private void DoResizeImage(Size size)
        {
            //Create a new resized image
            m_writeableBitmap = ResizeImage(m_writeableBitmap, size.Width, size.Height);

            Image = m_writeableBitmap;
        }
    }
}

